package mysql;

import com.goldhuman.Common.Marshal.OctetsStream;
import com.goldhuman.Common.Octets;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import xdb.Storage;
import xdb.Trace;
import xdb.Xdb;
import xdb.util.Misc;

import java.io.*;
import java.sql.*;
import java.util.*;

public class MySqlMgr {
    public static final String V8 = "8.0";
    private static final String DRIVER_CLASS_V5 = "com.mysql.jdbc.Driver";
    private static final String DRIVER_CLASS_V8 = "com.mysql.cj.jdbc.Driver";
    public static int MS_DEBUG = 30;//30毫秒
    public static int MS_INFO = 200;//200毫秒
    //如果失败，就从缓存清理，以后万一出现考虑不到存储的错误，用来清理错误缓存数据，避免影响正常数据存储
    public static Map<String, Boolean> failedClearMap = Misc.newConcurrentMap();
    private static MySqlMgr mysqlMgr = null;
    private HikariDataSource readConn = null;
    private HikariDataSource writeConn = null;
    private MySQLConf mySQLConf = null;
    private final Map<String, Integer> queryNullMap = Misc.newConcurrentMap();
    private final Map<String, Integer> queryDataMap = Misc.newConcurrentMap();

    private MySqlMgr(Properties conf) {
        try {
            mySQLConf = new MySQLConf();
            if (!Xdb.isDBServer()) {
                return;
            }

            if (!(conf == null ? mySQLConf.Init() : mySQLConf.Init(conf))) {
                Trace.error("MySQL Conf Init Error, Please Check File db.conf");
                Xdb.getInstance().stop();
                Runtime.getRuntime().halt(0);
            }

            MS_DEBUG = mySQLConf.getMsDebug();
            MS_INFO = mySQLConf.getMsInfo();
            readConn = new HikariDataSource(InitHikariConfig(true, mySQLConf.getMinPoolSize(), mySQLConf.getMaxPoolSize()));
            if (mySQLConf.isbCloseCache()) {
                writeConn = new HikariDataSource(InitHikariConfig(false, mySQLConf.getMinPoolSize(), mySQLConf.getMaxPoolSize()));
            } else {
                writeConn = new HikariDataSource(InitHikariConfig(false, 2, 2));
            }

            if (readConn != null && writeConn != null) {
                Trace.info("MySQL Connection Pool Init OK!");
            } else {
                Trace.error("MySQL Connection Pool Init Error, System Exit!");
                Xdb.getInstance().stop();
                Runtime.getRuntime().halt(0);
            }
            reloadfailedClearMap();
        } catch (Exception var3) {
            Trace.error("MySQL Connection Init Error!", var3);
            Xdb.getInstance().stop();
            Runtime.getRuntime().halt(0);
        }

    }

    public static MySqlMgr GetInstance() {
        if (mysqlMgr == null) {
            mysqlMgr = new MySqlMgr(null);
        }
        return mysqlMgr;
    }

    public static MySqlMgr GetInstance(Properties conf) {
        if (mysqlMgr == null) {
            mysqlMgr = new MySqlMgr(conf);
        }

        return mysqlMgr;
    }

    public static void main(String[] args) {
        GetInstance().ExecUpdate("DELETE FROM _meta_", null);
        GetInstance().Exit();
    }

    public static void reloadfailedClearMap() {
        failedClearMap.clear();
        try {
            File filename = new File("failedclear.txt");
            if (!filename.exists()) {
                return;
            }
            FileInputStream finput = new FileInputStream(filename);
            InputStreamReader reader = new InputStreamReader(finput);
            BufferedReader br = new BufferedReader(reader);
            String line = br.readLine();
            int count;
            for (count = 0; line != null; line = br.readLine()) {
                if (line.length() > 0) {
                    failedClearMap.put(line, true);
                    ++count;
                }
            }
            Trace.info("failedclear.txt total count=" + count);
            br.close();
            reader.close();
            finput.close();
        } catch (Exception e) {
            Trace.error("reloadfailedClearMap", e);
        }
    }

    private HikariConfig InitHikariConfig(boolean autoCommit, int minPoolSize, int maxPoolSize) {
        HikariConfig hikariConfig = new HikariConfig();
        if (V8.equals(mySQLConf.getVersion())) {
            hikariConfig.setDriverClassName(DRIVER_CLASS_V8);
            hikariConfig.setJdbcUrl("jdbc:mysql://" + mySQLConf.getIp() + ":" + mySQLConf.getPort() + "/" + mySQLConf.getDbName() + "?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC");
        } else {
            hikariConfig.setDriverClassName(DRIVER_CLASS_V5);
            hikariConfig.setJdbcUrl("jdbc:mysql://" + mySQLConf.getIp() + ":" + mySQLConf.getPort() + "/" + mySQLConf.getDbName() + "?useUnicode=true&characterEncoding=utf8&useSSL=false&rewriteBatchedStatements=true&connectTimeout=30000&socketTimeout=60000");
        }
        hikariConfig.setUsername(mySQLConf.getUser());
        hikariConfig.setPassword(mySQLConf.getPassword());
        hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
        hikariConfig.addDataSourceProperty("prepStmtCacheSize", 512);
        hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
        hikariConfig.setConnectionTimeout(1000L);//1 Second
        hikariConfig.setConnectionTestQuery("SELECT 1");
        hikariConfig.setAutoCommit(autoCommit);
        hikariConfig.setMinimumIdle(minPoolSize);
        hikariConfig.setMaximumPoolSize(maxPoolSize);
        return hikariConfig;
    }

    public String GetDBName() {
        return mySQLConf.getDbName();
    }

    public void Exit() {
        try {
            if (readConn != null) {
                readConn.close();
                Trace.info("Read Conn Close OK!");
            }
            if (writeConn != null) {
                writeConn.close();
                Trace.info("Write Conn Close OK!");
            }
        } catch (Exception e) {
            Trace.error("conn close Error!", e);
        }

    }

    public Connection GetReadConn() throws SQLException {
        long startTime = System.currentTimeMillis();
        try {
            return readConn.getConnection();
        } catch (Exception e) {
            Trace.error("read conn get Error!", e);
            //readConn.resumePool();
            throw new SQLException();
        } finally {
            long cost = System.currentTimeMillis() - startTime;
            if (cost > MS_DEBUG) {
                Trace.debug("read conn get cost too long! use" + cost);
            }

            if (cost > MS_INFO) {
                Trace.info("read conn get cost too long! use" + cost);
            }
        }
    }

    public Connection GetWriteConn() throws SQLException {
        try {
            return writeConn.getConnection();
        } catch (Exception e) {
            Trace.error("write conn get Error!", e);
            //writeConn.resumePool();
            throw new SQLException();
        }
    }

    public Set<String> GetTableNames() {
        try {
            String sql = "select table_name from information_schema.tables where table_schema='" + mySQLConf.getDbName() + "'";
            Set<String> tableNames = new HashSet();
            Connection conn = GetReadConn();
            PreparedStatement pst = conn.prepareStatement(sql);
            ResultSet ret = pst.executeQuery();

            while (ret.next()) {
                tableNames.add(ret.getString(1));
            }

            pst.close();
            conn.close();
            return tableNames;
        } catch (Exception e) {
            Trace.error("GetTableNames", e);
            return new HashSet<String>();
        }
    }

    public OctetsStream ExecQuery(String sql) throws SQLException {
        Connection conn = null;
        PreparedStatement pst = null;

        try {
            long tm = System.currentTimeMillis();
            OctetsStream octStream = null;
            conn = GetReadConn();
            pst = conn.prepareStatement(sql);
            pst.setQueryTimeout(2);//查询超时设置成2秒，避免存储过程超时
            ResultSet ret = pst.executeQuery();
            if (ret.next()) {
                if (Xdb.getInstance().isJson()) {
                    String value = ret.getString(1);
                    octStream = new OctetsStream().marshalJson(value);
                } else {
                    Blob v = ret.getBlob(1);
                    octStream = OctetsStream.wrap(Octets.wrap(v.getBytes(1L, (int) v.length())));
                }
                if (Trace.isDebugEnabled()) {
                    if (!queryDataMap.containsKey(sql)) {
                        queryDataMap.put(sql, 1);
                    } else {
                        queryDataMap.put(sql, queryDataMap.get(sql) + 1);
                    }
                    Trace.debug("Find Times " + queryDataMap.get(sql) + ", SQL is " + sql);
                }
            } else {
                if (Trace.isDebugEnabled()) {
                    if (!queryNullMap.containsKey(sql)) {
                        queryNullMap.put(sql, 1);
                    } else {
                        queryNullMap.put(sql, queryNullMap.get(sql) + 1);
                    }
                    Trace.debug("Not Find Times " + queryNullMap.get(sql) + ", SQL is " + sql);
                }
            }
            long useTime = System.currentTimeMillis() - tm;
            if (useTime > MS_DEBUG) {
                Trace.debug("Execute Query Use " + useTime + ", sql[" + sql + "]");
            }
            if (useTime > MS_INFO) {
                Trace.info("Execute Query Use " + useTime + ", sql[" + sql + "]");
            }
            return octStream;
        } catch (Exception e) {
            xdb.Trace.error("SQL is " + sql, e);
            throw new SQLException();
        } finally {
            if (pst != null) {
                try {
                    pst.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read pst close", e);
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read conn close", e);
                }
            }
        }
    }

    public int ExecUpdate(String sql, OctetsStream value) {
        Connection conn = null;
        try {
            long tm = System.currentTimeMillis();
            conn = GetWriteConn();
            PreparedStatement pst = conn.prepareStatement(sql);
            pst.setQueryTimeout(5);//更新超时设置成5秒，避免线程卡住
            if (value != null) {
                if (Xdb.getInstance().isJson()) {
                    pst.setString(1, value.unmarshalJson());
                } else {
                    pst.setBlob(1, new ByteArrayInputStream(Storage.getValueBytes(value.array(), value.size())));
                }
            }

            int ret = pst.executeUpdate();
            conn.commit();
            pst.close();
            long useTime = System.currentTimeMillis() - tm;
            if (useTime > MS_DEBUG) {
                if (Trace.isDebugEnabled()) {
                    Trace.debug("Execute Update Use " + useTime + " SQL is " + sql);
                }
            }
            if (useTime > MS_INFO) {
                Trace.info("Execute Update Use " + useTime + " SQL is " + sql);
            }
            return ret;
        } catch (Exception e) {
            Trace.error("SQL is " + sql, e);
            return -1;
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    Trace.error("write conn close", e);
                }
            }
        }
    }

    public Map<OctetsStream, OctetsStream> ExecQueryMulti(String sql) throws SQLException {
        Connection conn = null;
        PreparedStatement pst = null;
        Map<OctetsStream, OctetsStream> result = new HashMap();

        try {
            long tm = System.currentTimeMillis();
            conn = GetReadConn();
            pst = conn.prepareStatement(sql);
            pst.setQueryTimeout(2);//查询超时设置成2秒，避免存储过程超时
            ResultSet ret = pst.executeQuery();

            while (ret.next()) {
                String key = ret.getString(1);
                byte[] bs = Storage.getBytesByHexString(key);
                Octets oct = new Octets(bs);
                OctetsStream keyOctStream = new OctetsStream(oct);
                OctetsStream valueOctStream;
                if (Xdb.getInstance().isJson()) {
                    String value = ret.getString(2);
                    valueOctStream = (new OctetsStream()).marshalJson(value);
                } else {
                    Blob v = ret.getBlob(2);
                    valueOctStream = OctetsStream.wrap(Octets.wrap(v.getBytes(1L, (int) v.length())));
                }

                result.put(keyOctStream, valueOctStream);
                if (Trace.isDebugEnabled()) {
                    if (!queryDataMap.containsKey(sql)) {
                        queryDataMap.put(sql, 1);
                    } else {
                        queryDataMap.put(sql, queryDataMap.get(sql) + 1);
                    }

                    Trace.debug("Find Times " + queryDataMap.get(sql) + ", SQL is " + sql);
                }
            }

            long useTime = System.currentTimeMillis() - tm;
            if (useTime > (long) MS_DEBUG) {
                Trace.debug("Execute Query Use " + useTime + ", sql[" + sql + "]");
            }

            if (useTime > (long) MS_INFO) {
                Trace.info("Execute Query Use " + useTime + ", sql[" + sql + "]");
            }

            return result;
        } catch (Exception e) {
            Trace.error("SQL is " + sql, e);
            throw new SQLException();
        } finally {
            if (pst != null) {
                try {
                    pst.close();
                } catch (SQLException e) {
                    Trace.error("read pst close", e);
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    Trace.error("read conn close", e);
                }
            }
        }
    }

    public void InitMetaFile(String meta) {
        if (Xdb.getInstance().isJson()) {
            ExecUpdate("REPLACE INTO _meta_ VALUES(1, ?)", (new OctetsStream()).marshalJson(meta));
        } else {
            ExecUpdate("REPLACE INTO _meta_ VALUES(1, ?)", OctetsStream.wrap(Octets.wrap(meta.getBytes())));
        }

    }

    public String GetMetaFile() {
        if (isAutoSql()) {
            String sql = "CREATE TABLE IF NOT EXISTS _meta_(k INT NOT NULL PRIMARY KEY, v MEDIUMBLOB NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8";
            ExecUpdate(sql, null);
        }
        try {
            OctetsStream oct = ExecQuery("SELECT v from _meta_ WHERE k=1");
            if (oct == null) {
                return "";
            }
            if (Xdb.getInstance().isJson()) {
                return oct.unmarshalJson();
            } else {
                return new String(Storage.getValueBytes(oct.array(), oct.size()));
            }
        } catch (Exception e) {
            Trace.error("GetMetaFile", e);
            return null;
        }
    }

    public boolean isAutoSql() {
        return mySQLConf.isbAutoSql();
    }
}
